# -*- coding: utf-8 -*-
"""
Spyder Editor

This is a temporary script file.
"""

import tkinter as tk
from sys import exit
from random import randint
from random import choice
from tkinter import messagebox

color = {" "   :  "#DCDCDC",
         "2"   :  "#FF9F00",
         "4"   :  "#AD9F2F",
         "8"   :  "#228B22",
         "16"  :  "#D2691E",
         "32"  :  "#ADFF2F",
         "64"  :  "#5F9EA0",
         "128" :  "#FF4500",
         "256" :  "#00BFFF",
         "512" :  "#CD5C5C",
         "1024":  "#800080",
         "2048":  "#FFD700"}


class Window:
    def __init__(self, master):
        self.master = master
        # main frame
        self.fr = tk.Frame(master)
        self.fr.pack()
        # title
        self.title = tk.Label(self.fr,text = "2048", font=("Consolas",20))
        self.title.grid(row=1 ,columnspan=2)
        # score
        self.score = tk.Label(self.fr ,text="score: 0")
        self.score.grid(row=2 ,column=0)
        
        self.introduction = tk.Label(self.fr ,text = "\t\tw:^\n\t\ta:<\td:>\n\t\ts:v")
        self.introduction.grid(row=2, column=1, rowspan=2)
        
        self.fr_b = tk.Frame(self.fr)
        self.blocks = [[], [], [], []]        
        for i in range(16):
            row = i // 4
            column = i % 4
            label = tk.Label(self.fr_b, bg="#DCDCDC", width=8,
                             height=4 )
            self.blocks[row].append(label)
            label.grid(row=row, column=column)
        self.fr_b.grid(row=4, columnspan=2)

    
    def reprint(self, data, score):
        for i in range(16):
            row = i // 4
            column = i % 4
            d = data[row][column]
            c = str(d) if d else ' '
            self.blocks[row][column]["text"] = c
            self.blocks[row][column]["bg"] = color[c]
        self.score["text"] = "score: " + str(score)
        
    
    def fail(self, score):
        text = "Game Over. You score %d points.\nTry again?" % score
        try_again = messagebox.askyesno("Game Over :(" , text)
        return try_again
    
    def exit_sets(self):
        self.master.destroy()


class Nuclear:
    def __init__(self):
        self.score = 0
        self.data = [[], [], [], []]
        for i in range(16):
            row = i // 4
            self.data[row].append(0)
        self.data[randint(0, 3)][randint(0, 3)] = choice([2, 2, 2, 2, 2, 2, 4])
        self.put()
        
    def transpose(self, matrix):
        return list(map(list, zip(*matrix)))
        
    def _align(self, data):
        sc_get = 0
        temp = []
        for row in data:
            row = [x for x in row if x]
            for i in range(len(row) - 1):
                if row[i] == row[i + 1]:
                    row[i] += row[i]
                    row[i + 1] = 0
                    sc_get += row[i]
            row = [x for x in row if x]
            while len(row) < 4:
                row.append(0)
            temp.append(row)
        return sc_get, temp
    
    
    def _redire(self, data):
        temp = []
        for row in data:
            temp.append(row[::-1])
        return temp
    
    
    def _align_dir(self, dire):
        t = self.data
        s = 0
        if dire == "R":
            t = self._redire(self.data)
        elif dire == "U":
            t = self.transpose(self.data)
        elif dire == "D":
            t = self._redire(self.transpose(self.data))
        s, t = self._align(t)
        if dire == "R":
            t = self._redire(t)
        elif dire == "U":
            t = self.transpose(t)
        elif dire == "D":
            t = self.transpose(self._redire(t))
        return s, t
    
    def align(self, dire):
        s, t = self._align_dir(dire)
        if t != self.data:
            self.score += s
            self.data = t
            return True
        else:
            return False

        
    def is_fail(self):
        l = self._align_dir("L")[1]
        r = self._align_dir("R")[1]
        u = self._align_dir("U")[1]
        d = self._align_dir("D")[1]
        std = self.data
#        print("is fail: ",l == std and r == std and u == std and d == std)
        return l == std and r == std and u == std and d == std
    
    def put(self):
        v = 1
        i = j = 0
        while(v):
            i = randint(0, 3)
            j = randint(0, 3)
            v = self.data[i][j]
        self.data[i][j] = choice([2, 2, 2, 2, 4])
        
    
    def move(self, dire):
        if self.is_fail():
            return False
        else:
            if(self.align(dire)):
                self.put()
        return True
    
    def restart(self):
        self.__init__()
    
    
'''    
    def display(self):
        print('{0:4} {1:4} {2:4} {3:4}'.format(self.data[0][0], self.data[0][1], self.data[0][2], self.data[0][3]))
        print('{0:4} {1:4} {2:4} {3:4}'.format(self.data[1][0], self.data[1][1], self.data[1][2], self.data[1][3]))
        print('{0:4} {1:4} {2:4} {3:4}'.format(self.data[2][0], self.data[2][1], self.data[2][2], self.data[2][3]))
        print('{0:4} {1:4} {2:4} {3:4}'.format(self.data[3][0], self.data[3][1], self.data[3][2], self.data[3][3]))
        print('   Total score: ', self.score)
        print()
'''

class Event:
    def __init__(self, nuclear, holder, window):
        holder.bind("<Key>", self.dealEvents)
        self.nuclear = nuclear
        self.failed = False
        self.window = window
        self.window.reprint(self.nuclear.data, self.nuclear.score)


    def dealEvents(self, event):
        key = event.char
        key_to_dir = {"w":"U","s":"D","a":"L","d":"R"}
        if not self.failed:
#            print("input char: ", key)
            if key in key_to_dir.keys():
                self.failed = not self.nuclear.move(key_to_dir[key])
                self.window.reprint(self.nuclear.data, self.nuclear.score)
#                self.nuclear.display()
        else:
#            print("Failed!")
            try_again = self.window.fail(self.nuclear.score)
            if try_again:
                self.nuclear.restart()
                self.window.reprint(self.nuclear.data, self.nuclear.score)
                self.failed = False
            else:
                self.window.exit_sets()
                exit(0)


class App:
    def __init__(self):
        root = tk.Tk()
        root.title("2048")
        Event(Nuclear(), root, Window(root))
        root.mainloop()
    

if __name__ == "__main__":
    App()